#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
B站弹幕机 JMeter 测试结果分析脚本
作者: AI Assistant
版本: 1.0
"""

import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
import numpy as np
import json
import os
import sys
from datetime import datetime
import argparse

# 设置中文字体
plt.rcParams['font.sans-serif'] = ['SimHei', 'DejaVu Sans']
plt.rcParams['axes.unicode_minus'] = False

class JMeterResultAnalyzer:
    def __init__(self, result_file='test_results.jtl'):
        self.result_file = result_file
        self.df = None
        self.analysis_dir = 'analysis_results'
        
        # 创建分析结果目录
        os.makedirs(self.analysis_dir, exist_ok=True)
        
    def load_data(self):
        """加载JMeter测试结果数据"""
        try:
            if not os.path.exists(self.result_file):
                print(f"错误: 测试结果文件不存在: {self.result_file}")
                return False
                
            self.df = pd.read_csv(self.result_file)
            
            # 处理时间戳列 - 将毫秒时间戳转换为datetime
            if 'timeStamp' in self.df.columns:
                # JMeter的时间戳是毫秒级的，需要转换为秒级
                self.df['timeStamp'] = pd.to_datetime(self.df['timeStamp'], unit='ms')
            
            # 确保success列是布尔类型
            if 'success' in self.df.columns:
                self.df['success'] = self.df['success'].astype(bool)
            
            print(f"成功加载测试数据，共 {len(self.df)} 条记录")
            print(f"时间范围: {self.df['timeStamp'].min()} 到 {self.df['timeStamp'].max()}")
            return True
        except Exception as e:
            print(f"加载数据失败: {e}")
            return False
    
    def basic_statistics(self):
        """基础统计分析"""
        if self.df is None:
            return None
            
        # 计算测试持续时间
        test_duration = (self.df['timeStamp'].max() - self.df['timeStamp'].min()).total_seconds()
        
        stats = {
            '总请求数': len(self.df),
            '成功请求数': len(self.df[self.df['success'] == True]),
            '失败请求数': len(self.df[self.df['success'] == False]),
            '平均响应时间(ms)': self.df['elapsed'].mean(),
            '最小响应时间(ms)': self.df['elapsed'].min(),
            '最大响应时间(ms)': self.df['elapsed'].max(),
            '95%响应时间(ms)': self.df['elapsed'].quantile(0.95),
            '99%响应时间(ms)': self.df['elapsed'].quantile(0.99),
            '标准差(ms)': self.df['elapsed'].std(),
            '测试持续时间(秒)': test_duration,
            '吞吐量(请求/秒)': len(self.df) / test_duration if test_duration > 0 else 0
        }
        
        # 计算成功率
        stats['成功率(%)'] = (stats['成功请求数'] / stats['总请求数']) * 100
        
        return stats
    
    def generate_response_time_chart(self):
        """生成响应时间分布图"""
        if self.df is None or len(self.df) == 0:
            print("警告: 没有数据可以生成图表")
            return
            
        plt.figure(figsize=(15, 10))
        
        # 响应时间分布直方图
        plt.subplot(2, 3, 1)
        plt.hist(self.df['elapsed'], bins=50, alpha=0.7, color='skyblue', edgecolor='black')
        plt.title('响应时间分布')
        plt.xlabel('响应时间 (ms)')
        plt.ylabel('请求数量')
        plt.grid(True, alpha=0.3)
        
        # 响应时间箱线图
        plt.subplot(2, 3, 2)
        plt.boxplot(self.df['elapsed'])
        plt.title('响应时间箱线图')
        plt.ylabel('响应时间 (ms)')
        plt.grid(True, alpha=0.3)
        
        # 各接口平均响应时间
        plt.subplot(2, 3, 3)
        try:
            interface_stats = self.df.groupby('label')['elapsed'].mean().sort_values()
            if len(interface_stats) > 0:
                plt.barh(range(len(interface_stats)), interface_stats)
                plt.yticks(range(len(interface_stats)), interface_stats.index)
                plt.title('各接口平均响应时间')
                plt.xlabel('平均响应时间 (ms)')
                plt.grid(True, alpha=0.3)
            else:
                plt.text(0.5, 0.5, '没有接口数据', ha='center', va='center', transform=plt.gca().transAxes)
                plt.title('各接口平均响应时间')
        except Exception as e:
            print(f"生成接口统计图失败: {e}")
            plt.text(0.5, 0.5, '接口统计数据生成失败', ha='center', va='center', transform=plt.gca().transAxes)
            plt.title('各接口平均响应时间')
        
        # 响应时间随时间变化
        plt.subplot(2, 3, 4)
        try:
            # 使用10秒频率进行时间分组
            time_series = self.df.groupby(pd.Grouper(key='timeStamp', freq='10s'))['elapsed'].mean()
            if len(time_series) > 0:
                plt.plot(time_series.index, time_series, marker='o', markersize=3)
                plt.title('响应时间随时间变化')
                plt.xlabel('时间')
                plt.ylabel('平均响应时间 (ms)')
                plt.xticks(rotation=45)
                plt.grid(True, alpha=0.3)
            else:
                plt.text(0.5, 0.5, '没有时间序列数据', ha='center', va='center', transform=plt.gca().transAxes)
                plt.title('响应时间随时间变化')
        except Exception as e:
            print(f"生成时间序列图失败: {e}")
            plt.text(0.5, 0.5, '时间序列数据生成失败', ha='center', va='center', transform=plt.gca().transAxes)
            plt.title('响应时间随时间变化')
        
        # 成功/失败请求分布
        plt.subplot(2, 3, 5)
        try:
            success_counts = self.df['success'].value_counts()
            if len(success_counts) > 0:
                plt.pie(success_counts, labels=['成功' if x else '失败' for x in success_counts.index], 
                        autopct='%1.1f%%', startangle=90)
                plt.title('请求成功/失败分布')
            else:
                plt.text(0.5, 0.5, '没有成功/失败数据', ha='center', va='center', transform=plt.gca().transAxes)
                plt.title('请求成功/失败分布')
        except Exception as e:
            print(f"生成成功/失败分布图失败: {e}")
            plt.text(0.5, 0.5, '成功/失败数据生成失败', ha='center', va='center', transform=plt.gca().transAxes)
            plt.title('请求成功/失败分布')
        
        # 各接口请求数量
        plt.subplot(2, 3, 6)
        try:
            interface_counts = self.df['label'].value_counts()
            if len(interface_counts) > 0:
                plt.bar(range(len(interface_counts)), interface_counts)
                plt.xticks(range(len(interface_counts)), interface_counts.index, rotation=45, ha='right')
                plt.title('各接口请求数量')
                plt.ylabel('请求数量')
                plt.grid(True, alpha=0.3)
            else:
                plt.text(0.5, 0.5, '没有接口请求数据', ha='center', va='center', transform=plt.gca().transAxes)
                plt.title('各接口请求数量')
        except Exception as e:
            print(f"生成接口请求数量图失败: {e}")
            plt.text(0.5, 0.5, '接口请求数据生成失败', ha='center', va='center', transform=plt.gca().transAxes)
            plt.title('各接口请求数量')
        
        plt.tight_layout()
        plt.savefig(f'{self.analysis_dir}/response_time_analysis.png', dpi=300, bbox_inches='tight')
        plt.close()
        
        print(f"响应时间分析图表已保存: {self.analysis_dir}/response_time_analysis.png")
    
    def generate_performance_chart(self):
        """生成性能分析图表"""
        if self.df is None or len(self.df) == 0:
            print("警告: 没有数据可以生成图表")
            return
            
        plt.figure(figsize=(15, 10))
        
        # 吞吐量分析
        plt.subplot(2, 2, 1)
        try:
            throughput_data = self.df.groupby(pd.Grouper(key='timeStamp', freq='10s')).size()
            if len(throughput_data) > 0:
                plt.plot(throughput_data.index, throughput_data, marker='o', markersize=3, color='green')
                plt.title('吞吐量随时间变化')
                plt.xlabel('时间')
                plt.ylabel('请求数量 (10秒内)')
                plt.xticks(rotation=45)
                plt.grid(True, alpha=0.3)
            else:
                plt.text(0.5, 0.5, '没有吞吐量数据', ha='center', va='center', transform=plt.gca().transAxes)
                plt.title('吞吐量随时间变化')
        except Exception as e:
            print(f"生成吞吐量图失败: {e}")
            plt.text(0.5, 0.5, '吞吐量数据生成失败', ha='center', va='center', transform=plt.gca().transAxes)
            plt.title('吞吐量随时间变化')
        
        # 错误率分析
        plt.subplot(2, 2, 2)
        try:
            error_rate_data = self.df.groupby(pd.Grouper(key='timeStamp', freq='10s'))['success'].apply(
                lambda x: (x == False).sum() / len(x) * 100
            )
            if len(error_rate_data) > 0:
                plt.plot(error_rate_data.index, error_rate_data, marker='o', markersize=3, color='red')
                plt.title('错误率随时间变化')
                plt.xlabel('时间')
                plt.ylabel('错误率 (%)')
                plt.xticks(rotation=45)
                plt.grid(True, alpha=0.3)
            else:
                plt.text(0.5, 0.5, '没有错误率数据', ha='center', va='center', transform=plt.gca().transAxes)
                plt.title('错误率随时间变化')
        except Exception as e:
            print(f"生成错误率图失败: {e}")
            plt.text(0.5, 0.5, '错误率数据生成失败', ha='center', va='center', transform=plt.gca().transAxes)
            plt.title('错误率随时间变化')
        
        # 响应时间热力图
        plt.subplot(2, 2, 3)
        try:
            # 创建时间-接口响应时间矩阵
            pivot_data = self.df.pivot_table(
                values='elapsed', 
                index=pd.Grouper(key='timeStamp', freq='30s'), 
                columns='label', 
                aggfunc='mean'
            )
            if not pivot_data.empty:
                sns.heatmap(pivot_data.T, cmap='YlOrRd', cbar_kws={'label': '响应时间 (ms)'})
                plt.title('响应时间热力图')
                plt.xlabel('时间')
                plt.ylabel('接口')
            else:
                plt.text(0.5, 0.5, '没有热力图数据', ha='center', va='center', transform=plt.gca().transAxes)
                plt.title('响应时间热力图')
        except Exception as e:
            print(f"生成热力图失败: {e}")
            plt.text(0.5, 0.5, '热力图数据生成失败', ha='center', va='center', transform=plt.gca().transAxes)
            plt.title('响应时间热力图')
        
        # 接口性能对比
        plt.subplot(2, 2, 4)
        try:
            # 计算各接口的平均响应时间和成功率
            interface_stats = {}
            for label in self.df['label'].unique():
                interface_data = self.df[self.df['label'] == label]
                interface_stats[label] = {
                    'mean_response': interface_data['elapsed'].mean(),
                    'success_rate': (interface_data['success'] == True).sum() / len(interface_data) * 100
                }
            
            if interface_stats:
                labels = list(interface_stats.keys())
                mean_responses = [interface_stats[label]['mean_response'] for label in labels]
                success_rates = [interface_stats[label]['success_rate'] for label in labels]
                
                x_pos = np.arange(len(labels))
                plt.bar(x_pos - 0.2, mean_responses, 0.4, label='平均响应时间(ms)', alpha=0.8)
                plt.bar(x_pos + 0.2, success_rates, 0.4, label='成功率(%)', alpha=0.8)
                
                plt.xlabel('接口')
                plt.ylabel('数值')
                plt.title('接口性能对比')
                plt.xticks(x_pos, labels, rotation=45, ha='right')
                plt.legend()
                plt.grid(True, alpha=0.3)
            else:
                plt.text(0.5, 0.5, '没有性能对比数据', ha='center', va='center', transform=plt.gca().transAxes)
                plt.title('接口性能对比')
        except Exception as e:
            print(f"生成性能对比图失败: {e}")
            plt.text(0.5, 0.5, '性能对比数据生成失败', ha='center', va='center', transform=plt.gca().transAxes)
            plt.title('接口性能对比')
        
        plt.tight_layout()
        plt.savefig(f'{self.analysis_dir}/performance_analysis.png', dpi=300, bbox_inches='tight')
        plt.close()
        
        print(f"性能分析图表已保存: {self.analysis_dir}/performance_analysis.png")
    
    def generate_detailed_report(self, stats):
        """生成详细分析报告"""
        report = {
            '测试信息': {
                '测试时间': datetime.now().strftime('%Y-%m-%d %H:%M:%S'),
                '测试文件': self.result_file,
                '数据记录数': len(self.df) if self.df is not None else 0
            },
            '基础统计': stats,
            '接口分析': {}
        }
        
        if self.df is not None:
            # 各接口详细分析
            for interface in self.df['label'].unique():
                interface_data = self.df[self.df['label'] == interface]
                report['接口分析'][interface] = {
                    '请求数量': len(interface_data),
                    '成功数量': len(interface_data[interface_data['success'] == True]),
                    '失败数量': len(interface_data[interface_data['success'] == False]),
                    '成功率(%)': round(len(interface_data[interface_data['success'] == True]) / len(interface_data) * 100, 2),
                    '平均响应时间(ms)': round(interface_data['elapsed'].mean(), 2),
                    '最小响应时间(ms)': interface_data['elapsed'].min(),
                    '最大响应时间(ms)': interface_data['elapsed'].max(),
                    '95%响应时间(ms)': round(interface_data['elapsed'].quantile(0.95), 2)
                }
        
        # 保存JSON报告
        with open(f'{self.analysis_dir}/detailed_report.json', 'w', encoding='utf-8') as f:
            json.dump(report, f, ensure_ascii=False, indent=2)
        
        # 生成HTML报告
        self.generate_html_report(report)
        
        print(f"详细报告已保存: {self.analysis_dir}/detailed_report.json")
        print(f"HTML报告已保存: {self.analysis_dir}/report.html")
    
    def generate_html_report(self, report):
        """生成HTML格式的报告"""
        html_content = f"""
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>B站弹幕机 JMeter 测试报告</title>
    <style>
        body {{ font-family: 'Microsoft YaHei', Arial, sans-serif; margin: 20px; background-color: #f5f5f5; }}
        .container {{ max-width: 1200px; margin: 0 auto; background-color: white; padding: 20px; border-radius: 8px; box-shadow: 0 2px 10px rgba(0,0,0,0.1); }}
        h1 {{ color: #333; text-align: center; border-bottom: 2px solid #007bff; padding-bottom: 10px; }}
        h2 {{ color: #007bff; margin-top: 30px; }}
        .stats-grid {{ display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 15px; margin: 20px 0; }}
        .stat-card {{ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; padding: 15px; border-radius: 8px; text-align: center; }}
        .stat-value {{ font-size: 24px; font-weight: bold; margin: 5px 0; }}
        .stat-label {{ font-size: 14px; opacity: 0.9; }}
        table {{ width: 100%; border-collapse: collapse; margin: 20px 0; }}
        th, td {{ padding: 12px; text-align: left; border-bottom: 1px solid #ddd; }}
        th {{ background-color: #f8f9fa; font-weight: bold; }}
        tr:hover {{ background-color: #f5f5f5; }}
        .success {{ color: #28a745; }}
        .warning {{ color: #ffc107; }}
        .danger {{ color: #dc3545; }}
        .chart-container {{ margin: 20px 0; text-align: center; }}
        .chart-container img {{ max-width: 100%; height: auto; border-radius: 8px; box-shadow: 0 2px 10px rgba(0,0,0,0.1); }}
    </style>
</head>
<body>
    <div class="container">
        <h1>B站弹幕机 JMeter 测试报告</h1>
        
        <h2>测试信息</h2>
        <table>
            <tr><th>测试时间</th><td>{report['测试信息']['测试时间']}</td></tr>
            <tr><th>测试文件</th><td>{report['测试信息']['测试文件']}</td></tr>
            <tr><th>数据记录数</th><td>{report['测试信息']['数据记录数']}</td></tr>
        </table>
        
        <h2>基础统计</h2>
        <div class="stats-grid">
            <div class="stat-card">
                <div class="stat-value">{report['基础统计']['总请求数']}</div>
                <div class="stat-label">总请求数</div>
            </div>
            <div class="stat-card">
                <div class="stat-value">{report['基础统计']['成功请求数']}</div>
                <div class="stat-label">成功请求数</div>
            </div>
            <div class="stat-card">
                <div class="stat-value">{report['基础统计']['失败请求数']}</div>
                <div class="stat-label">失败请求数</div>
            </div>
            <div class="stat-card">
                <div class="stat-value">{report['基础统计']['成功率(%)']:.1f}%</div>
                <div class="stat-label">成功率</div>
            </div>
            <div class="stat-card">
                <div class="stat-value">{report['基础统计']['平均响应时间(ms)']:.1f}ms</div>
                <div class="stat-label">平均响应时间</div>
            </div>
            <div class="stat-card">
                <div class="stat-value">{report['基础统计']['吞吐量(请求/秒)']:.1f}/s</div>
                <div class="stat-label">吞吐量</div>
            </div>
        </div>
        
        <h2>接口详细分析</h2>
        <table>
            <thead>
                <tr>
                    <th>接口名称</th>
                    <th>请求数量</th>
                    <th>成功数量</th>
                    <th>失败数量</th>
                    <th>成功率</th>
                    <th>平均响应时间</th>
                    <th>95%响应时间</th>
                </tr>
            </thead>
            <tbody>
"""
        
        for interface, data in report['接口分析'].items():
            success_class = 'success' if data['成功率(%)'] >= 95 else 'warning' if data['成功率(%)'] >= 80 else 'danger'
            html_content += f"""
                <tr>
                    <td>{interface}</td>
                    <td>{data['请求数量']}</td>
                    <td>{data['成功数量']}</td>
                    <td>{data['失败数量']}</td>
                    <td class="{success_class}">{data['成功率(%)']}%</td>
                    <td>{data['平均响应时间(ms)']}ms</td>
                    <td>{data['95%响应时间(ms)']}ms</td>
                </tr>
"""
        
        html_content += """
            </tbody>
        </table>
        
        <h2>性能分析图表</h2>
        <div class="chart-container">
            <h3>响应时间分析</h3>
            <img src="response_time_analysis.png" alt="响应时间分析">
        </div>
        
        <div class="chart-container">
            <h3>性能分析</h3>
            <img src="performance_analysis.png" alt="性能分析">
        </div>
    </div>
</body>
</html>
"""
        
        with open(f'{self.analysis_dir}/report.html', 'w', encoding='utf-8') as f:
            f.write(html_content)
    
    def run_analysis(self):
        """运行完整分析"""
        print("开始分析JMeter测试结果...")
        
        if not self.load_data():
            return False
        
        # 基础统计
        stats = self.basic_statistics()
        if stats:
            print("\n=== 基础统计 ===")
            for key, value in stats.items():
                print(f"{key}: {value}")
        
        # 生成图表
        print("\n生成分析图表...")
        self.generate_response_time_chart()
        self.generate_performance_chart()
        
        # 生成报告
        print("\n生成详细报告...")
        self.generate_detailed_report(stats)
        
        print(f"\n分析完成！结果保存在 {self.analysis_dir} 目录中")
        return True

def main():
    parser = argparse.ArgumentParser(description='JMeter测试结果分析工具')
    parser.add_argument('-f', '--file', default='test_results.jtl', 
                       help='JMeter测试结果文件路径 (默认: test_results.jtl)')
    parser.add_argument('-o', '--output', default='analysis_results',
                       help='分析结果输出目录 (默认: analysis_results)')
    
    args = parser.parse_args()
    
    analyzer = JMeterResultAnalyzer(args.file)
    analyzer.analysis_dir = args.output
    
    if analyzer.run_analysis():
        print("分析成功完成！")
        sys.exit(0)
    else:
        print("分析失败！")
        sys.exit(1)

if __name__ == '__main__':
    main() 